﻿using System;
using System.Collections.Generic;
using System.Windows;

namespace Sudoku.Game.Concrete
{
    internal class Generator
    {
        // the grid with the numbers
        private int[,] grid;
        // random and array list
        private Random ran;
        private List<int> al;
        // number of row and column in a region
        private int size;
        private int regionSize;

        // size of the Sudoku grid is received as parameter
        // actually not the size of the grid but the size of the region
        public Generator(int size)
        {
            regionSize = size;
            this.size = size*size;
            // random number generator
            ran = new Random();
            // arraylist that will contain the possible values for every case in the grid
            al = new List<int>();

        }

        // call to generate a new grid
        public void generate(Boolean traceOn)
        {
            // start by row 0
            int currentRow = 0;
            // to count the startOver
            int[] trials = new int[size];
            // this this the grid that we will fill
            grid = new int[size,size];
            // now let's fill the grid row by row
            while (currentRow < size)
            {
                trials[currentRow]++;
                // try to generate the row if it works pass to next roow
                if (genRow(currentRow))
                {
                    currentRow++;
                    continue;
                }
                // so it didn't work check our count
                if (trials[currentRow] < regionSize*regionSize*regionSize*6)
                {
                    continue;
                }
                // so despite all our effort it does not fit we will have to restart for the whole
                // row regions
                while (currentRow%regionSize != 0)
                {
                    trials[currentRow--] = 0;
                }
                trials[currentRow] = 0;
            }
            // ok our grid is filled with 0-size but sudoku grids do not have 0
            for (int i = 0; i < size; i++)
            {
                for (int j = 0; j < size; j++)
                {
                    grid[i, j]++;
                }
            }
        }

        // try to generate an return true if it worked
        // (it might be impossible to fill the row for example if the only element left in the row
        //  is 5 so the last column must be 5 but I may already have a 5 in the column or region)
        private Boolean genRow(int row)
        {
            // for every column in the row
            for (int col = 0; col < size; col++)
            {
                // fill the arrayList of available value if no value abort
                if (fillArrayList(row, col) == 0)
                {
                    return false;
                }
                // ok I can retrieve a random value from the arrayList
                var id = ran.Next(al.Count);
                grid[row, col] = al[id];
                al.RemoveAt(id);
            }
            return true;
        }

        // fill the ArrayList with all available number for that row,col
        // returns the number of elements in the arraylist
        private int fillArrayList(int row, int col)
        {
            Boolean[] available = new Boolean[size];
            // flag all the slot as available
            for (int i = 0; i < size; i++)
                available[i] = true;

            // remove the number already used in row
            for (int i = 0; i < row; i++)
                available[grid[i, col]] = false;
            // remove the number already used in col
            for (int i = 0; i < col; i++)
                available[grid[row, i]] = false;
            // now the region. I just have to take care of the row over me in
            // the region the columns to the left of my position have already been checked as unavailable
            Point rowRange = getRegionRowsOrCols(row);
            Point colRange = getRegionRowsOrCols(col);
            for (int i = (int) rowRange.X; i < row; i++)
            {
                for (int j = (int) colRange.X; j <= colRange.Y; j++)
                {
                    available[grid[i, j]] = false;
                }
            }

            // empty the arrayList
            al.Clear();
            // fill it with all still available numbers
            for (int i = 0; i < size; i++)
            {
                if (available[i])
                    al.Add(i);
            }
            return al.Count;
        }

        // return the first and last row/column of the region into which is located the (row or col)
        private Point getRegionRowsOrCols(int rowOrCol)
        {
            int x = (rowOrCol/regionSize)*regionSize;
            int y = x + regionSize - 1;
            Point point = new Point(x, y);
            return point;
        }


        // to retrieve the grid
        public int[,] getGrid()
        {
            return grid;
        }
    }
}
